# -*- coding:utf-8 -*-
import numpy as np
import pandas


#声明一个类用于基于物品相似度的推荐系统
class item_similarity_recommender_py():
    def __init__(self):
        self.train_data = None
        self.user_id = None
        self.item_id = None
        self.cooccurence_matrix = None
        self.songs_dict = None
        self.rev_songs_dict = None
        self.item_similarity_recommendations = None
        
    #从数据集中抽取某一个用户听过的歌曲目录，并返回歌曲目录列表
    #因为这里是基于歌曲相似度的推荐，因此要抽出该用户已经听过的歌曲用于后面歌曲相似度的计算
    def get_user_items(self, user):
        user_data = self.train_data[self.train_data[self.user_id] == user]
        user_items = list(user_data[self.item_id].unique())
        
        return user_items
        
    #从数据集中抽取听过某一首歌的所有用户，并返回用户集合
    def get_item_users(self, item):
        item_data = self.train_data[self.train_data[self.item_id] == item]
        item_users = set(item_data[self.user_id].unique())
            
        return item_users
        
    #对训练样本进行去重，并返回所有歌曲目录
    def get_all_items_train_data(self):
        all_items = list(self.train_data[self.item_id].unique())
            
        return all_items
        
    #歌曲相似度关系矩阵
    def construct_cooccurence_matrix(self, user_songs, all_songs):
            
        #要进行歌曲的相似度的计算，首先要知道每首歌有多少人听，评分怎么样，然后根据评分进行相似度的计算
        user_songs_users = []        
        for i in range(0, len(user_songs)):#找到当前要预测的用户听过的歌曲，每一首都有那些人听
            user_songs_users.append(self.get_item_users(user_songs[i]))
            
        #生成len(user_songs)*len(all_songs)的0元素矩阵，用来存放评分
        cooccurence_matrix = np.matrix(np.zeros(shape=(len(user_songs), len(all_songs))), float)
           
        for i in range(0,len(all_songs)):
            #遍历所有的歌曲目录，依次计算相似度
            #找出所有歌曲中当前歌曲听过的所有用户
            songs_i_data = self.train_data[self.train_data[self.item_id] == all_songs[i]]
            users_i = set(songs_i_data[self.user_id].unique())
            
            for j in range(0,len(user_songs)):       
                    
                #获取当前歌曲听的用户数据
                users_j = user_songs_users[j]
                    
                #求取两首歌曲的交集，这里使用两首歌曲的交集处以两首歌曲的并集作为分数
                users_intersection = users_i.intersection(users_j)
                
                if len(users_intersection) != 0:
                    #求取两首歌曲用户的并集
                    users_union = users_i.union(users_j)
                    
                    cooccurence_matrix[j,i] = float(len(users_intersection))/float(len(users_union))
                else:
                    cooccurence_matrix[j,i] = 0
                    
        
        return cooccurence_matrix

    
    #使用歌曲相似度矩阵进行用户歌曲推荐
    def generate_top_recommendations(self, user, cooccurence_matrix, all_songs, user_songs):
        print("Number of zero values in cooccurence_matrix :%d" % np.count_nonzero(cooccurence_matrix))
        
		#每一列相加之后处理总的行数，也就是每一首歌曲的得分
        user_sim_scores = cooccurence_matrix.sum(axis=0)/float(cooccurence_matrix.shape[0])  #axis=0按行相加
        user_sim_scores = np.array(user_sim_scores)[0].tolist()
        #得到所有歌曲曲目中与当前用户听过歌曲相似性比较大的
        
        
        #对所有歌曲根据与用户听过的歌曲相似度进行排序
        sort_index = sorted(((e,i) for i,e in enumerate(list(user_sim_scores))), reverse=True)
    
        #Create a dataframe from the following
        columns = ['user_id', 'song', 'score', 'rank']
        df = pandas.DataFrame(columns=columns)
         
        #Fill the dataframe with top 20 item based recommendations
        rank = 1 
        for i in range(0,len(sort_index)):
            if ~np.isnan(sort_index[i][0]) and all_songs[sort_index[i][1]] not in user_songs and rank <= 20:
                df.loc[len(df)]=[user,all_songs[sort_index[i][1]],sort_index[i][0],rank]
                rank = rank+1
        
        #当没有歌曲可以推荐时执行如下代码
        if df.shape[0] == 0:
            print("The current user has no songs for training the item similarity based recommendation model.")
            return -1
        else:
            return df
 
    #创建一个item_similarity_recommender_py对象，并将训练数据传入
    def fit(self, train_data, user_id, item_id):
        self.train_data = train_data
        self.user_id = user_id
        self.item_id = item_id

    #根据每个用户听过的音乐目录进行推荐
    def predict(self, user):
        
        #获取该用户听过的歌曲目录，下面进行歌曲的相似度计算
        user_songs = self.get_user_items(user)    
        #输出该用户听过的歌曲的数目    
        print("Number of unique songs for the user: %d" % len(user_songs))
        
        #获取训练集上总共还有歌曲目录
        all_songs = self.get_all_items_train_data()
        
        print("Number of unique songs in the training set: %d" % len(all_songs))
         
        #计算歌曲的相似度，这里计算当前用户听过的歌曲与其他所有歌曲的相似度，找出相似度高的歌曲进行推荐
        cooccurence_matrix = self.construct_cooccurence_matrix(user_songs, all_songs)
        
        #根据歌曲相似度矩阵进行用户歌曲推荐
        df_recommendations = self.generate_top_recommendations(user, cooccurence_matrix, all_songs, user_songs)
                
        return df_recommendations
    
    #Get similar items to given items
    def get_similar_items(self, item_list):
        
        user_songs = item_list
        
        all_songs = self.get_all_items_train_data()
        
        print("no. of unique songs in the training set: %d" % len(all_songs))
         
        cooccurence_matrix = self.construct_cooccurence_matrix(user_songs, all_songs)
        
        user = ""
        df_recommendations = self.generate_top_recommendations(user, cooccurence_matrix, all_songs, user_songs)
         
        return df_recommendations